#include <iostream>

#include "xlcomm/base/date_time.h"
#include "xlcomm/net/endian.h"
#include "xlcomm/net/tcp_server.h"

using namespace xlcomm;

class DaytimeServer {
 public:
  DaytimeServer(net::EventLoop* loop, const net::InetAddress listen_addr)
      : loop_(loop), server_(loop, listen_addr, "DaytimeServer") {
    server_.set_connect_callback(
        std::bind(&DaytimeServer::OnConnect, this, _1));
  }

  void Start() {
    // Start绝对不能在构造函数调用，这么做将来会有线程安全的问题
    server_.Start(0);
    std::cout << server_.name() << " is listening in " << server_.ip_port()
              << std::endl;
  }

 private:
  void OnConnect(const net::TcpConnectionPtr& conn) {
    std::string daytime = DateTime::Format(DateTime::Now());
    conn->Send(daytime + "\n");
    conn->Shutdown();
  }

  net::EventLoop* loop_;
  net::TcpServer server_;
};

class TimeServer {
 public:
  TimeServer(net::EventLoop* loop, const net::InetAddress listen_addr)
      : loop_(loop), server_(loop, listen_addr, "TimeServer") {
    server_.set_connect_callback(std::bind(&TimeServer::OnConnect, this, _1));
  }

  void Start() {
    server_.Start(0);
    std::cout << server_.name() << " is listening in " << server_.ip_port()
              << std::endl;
  }

 private:
  void OnConnect(const net::TcpConnectionPtr& conn) {
    Timestamp now = DateTime::NowTimestamp();
    int64_t net = net::HostToNetwork64(now);
    conn->Send(&net, sizeof(net));
    conn->Shutdown();
  }

  net::EventLoop* loop_;
  net::TcpServer server_;
};

class EchoServer {
 public:
  EchoServer(net::EventLoop* loop, const net::InetAddress listen_addr)
      : loop_(loop),
        server_(loop, listen_addr, "EchoServer"),
        num_connected_(0) {
    server_.set_connect_callback(
        std::bind(&EchoServer::OnConnection, this, _1));
    server_.set_disconnect_callback(
        std::bind(&EchoServer::OnConnection, this, _1));
    server_.set_message_callback(
        std::bind(&EchoServer::OnMessage, this, _1, _2, _3));
  }

  void Start() {
    server_.Start(0);
    std::cout << server_.name() << " is listening in " << server_.ip_port()
              << std::endl;
  }

 private:
  void OnConnection(const net::TcpConnectionPtr& conn) {
    if (conn->IsConnected()) {
      conn->Send("hello, I'm EchoServer\n");
      ++num_connected_;
      if (num_connected_ > kMaxConnections) {
        conn->Shutdown();
      }
      conn->SetOutbufHighWaterMark(
          std::bind(&EchoServer::OnOutbuffHighWater, this, _1, _2), 1024);
    } else {
      --num_connected_;
    }
  }

  void OnMessage(const net::TcpConnectionPtr& conn, net::Buffer* buff,
                 Timestamp recv_time) {
    std::string msg = buff->ReadAllAsString();
    std::cout << server_.name() << " receive " << msg.size() << " bytes from "
              << conn->peer_addr().ToIpPort() << " at "
              << DateTime::Format(recv_time) << ", msg:" << msg << std::endl;
    conn->Send(msg);
  }

  void OnOutbuffHighWater(const net::TcpConnectionPtr& conn,
                          size_t remain_bytes) {
    std::cout << "----OnOutbuffHighWater!!!\n";
    conn->ForceClose();
  }

  net::EventLoop* loop_;
  net::TcpServer server_;
  std::atomic<int> num_connected_;
  const int kMaxConnections = 1024;
};

class ChargenServer {
 public:
  ChargenServer(net::EventLoop* loop, const net::InetAddress& listen_addr)
      : loop_(loop),
        server_(loop, listen_addr, "ChargenServer"),
        trans_bytes(0) {
    std::string line;
    for (int i = 33; i < 127; ++i) {
      line.push_back(char(i));
    }
    line += line;

    for (size_t i = 0; i < 127 - 33; ++i) {
      message_ += line.substr(i, 72) + '\n';
    }

    server_.set_connect_callback(
        std::bind(&ChargenServer::OnConnect, this, _1));
    server_.set_write_complete_callback(
        std::bind(&ChargenServer::OnWriteComplete, this, _1));
  }

  void Start(int thread_num) {
    server_.Start(thread_num);
    timing_.Start();
    loop_->RunEvery(kStaticSecond,
                    std::bind(&ChargenServer::PrintThroughput, this));
    std::cout << server_.name() << " is listening in " << server_.ip_port()
              << std::endl;
  }

 private:
  static const int kStaticSecond = 3;

  void OnConnect(const net::TcpConnectionPtr& conn) {
    conn->SetTcpNoDelay(true);
    SendMessage(conn);
  }

  void OnWriteComplete(const net::TcpConnectionPtr& conn) { SendMessage(conn); }

  void SendMessage(const net::TcpConnectionPtr& conn) {
    conn->Send(message_);
    trans_bytes += message_.length();
  }

  void PrintThroughput() {
    int64_t interval = timing_.Stop();
    double speed = static_cast<double>(trans_bytes) / 1024.0 / 1024.0 /
                   static_cast<double>(interval) *
                   DateTime::kMicroSecondsPerSecond;
    printf("ChargenServer send speed: %4.3fM/s\n", speed);
    timing_.Start();
    trans_bytes = 0;
  }

  net::EventLoop* loop_;
  net::TcpServer server_;
  std::atomic<int64_t> trans_bytes;
  Timing timing_;
  std::string message_;
};

int main(int argc, const char* argv[]) {
  net::EventLoop loop;

  DaytimeServer daytime_server(&loop, net::InetAddress(10001));
  daytime_server.Start();
  TimeServer time_server(&loop, net::InetAddress(10002));
  time_server.Start();
  EchoServer echo_server(&loop, net::InetAddress(10003));
  echo_server.Start();
  ChargenServer chargen_server(&loop, net::InetAddress(10004));
  chargen_server.Start(3);

  loop.Loop();
  return 0;
}